﻿// WebcamCaptureAForge
// 
// Webcam capturing wrapper for AForge library 
// 
// AForge was chosen for webcam capturing because OpenCV
// turned out to have no means for detecting that 
// webcam is disconnected.
// 
// FLMScan - Featured by FabLab Munich


using System;
using System.Collections.Generic;
using AForge.Video;
using AForge.Video.DirectShow;
using System.Drawing;
using System.Windows.Threading;
using log4net;
using FLMScan.Exception;

namespace FLMScan.Capture
{
    class WebcamCaptureAForge : IDevice, IDisposable
    {
        private ILog logger;
        private bool isDisposed = false;
        private bool isConnected = false;
        private int deviceIndex = 0;
        private int resolutionX = 640;
        private int resolutionY = 400;
        private VideoCaptureDevice captureDevice;
        private Bitmap lastCapturedFrame;

        private const string LogWebcamDeviceInitializationFailed = "Webcam initialization failed. Webcam probably not connected or invalid webcam device index ";
        private const string LogWebcamDeviceInitializationSucceeded = "Webcam initialization succeeded. Connected webcam ";

        public WebcamCaptureAForge(int deviceIndex, int resolutionX, int resolutionY)
        {
            logger = LogManager.GetLogger(typeof(WebcamCapture));
            this.deviceIndex = deviceIndex;
            this.resolutionX = resolutionX;
            this.resolutionY = resolutionY;

            try
            {
                Initialize();
            }
            catch (CaptureException)
            {
                // tolerate capturing exception on startup
                logger.Warn(LogWebcamDeviceInitializationFailed + deviceIndex);                
            }  
        }

        public bool IsConnected
        {
            get { return isConnected; }
        }

        public CapturedFrame GetFrame()
        {
            CapturedFrame frame = null;

            if (lastCapturedFrame != null)
            {
                frame = new CapturedFrame((Bitmap)lastCapturedFrame.Clone());
            }
            
            return frame;
        }

        public void Initialize()
        {
            try
            {
                // retrieve available video devices
                FilterInfoCollection videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);

                if (videoDevices.Count > 0)
                {
                    // requested video device index is valid
                    if (deviceIndex < videoDevices.Count)
                    {
                        string videoDeviceName = videoDevices[deviceIndex].Name;
                        string videoDeviceMoniker = videoDevices[deviceIndex].MonikerString;

                        captureDevice = new VideoCaptureDevice(videoDeviceMoniker);
                        captureDevice.NewFrame += new NewFrameEventHandler(video_NewFrame);
                        CloseVideoSource();                        
                        captureDevice.DesiredFrameRate = 10;

                        Size preferredSize = new Size(resolutionX, resolutionY);
                        Size actualSize = new Size();

                        // check video capabilities and take size that matches 
                        // size in preferences best
                        VideoCapabilities[] videoCapabilities = captureDevice.VideoCapabilities;
                        foreach(VideoCapabilities cap in videoCapabilities)
                        {
                            Size currentSize = cap.FrameSize;

                            // preferred width matches
                            if (currentSize.Width == preferredSize.Width)
                            {
                                // preferred height matches => take this size
                                if (currentSize.Height == preferredSize.Height)
                                {
                                    actualSize = currentSize;
                                    break;
                                }
                                // store this size, but search remaining sizes for better match
                                else
                                {
                                    actualSize = currentSize;
                                }
                            }
                        }

                        if( actualSize != Size.Empty )
                            captureDevice.DesiredFrameSize = actualSize;

                        captureDevice.Start();

                        isConnected = true;
                        logger.Info(LogWebcamDeviceInitializationSucceeded + videoDeviceName);
                    }
                    else
                    {
                        throw new CaptureException("Invalid webcam deviceIndex! " + deviceIndex);
                    }
                }
            }
            catch (System.Exception ex)
            {
                throw new CaptureException("Webcam initialization failed! Exception caught.", ex);
            }
        }

        private void video_NewFrame(object sender, NewFrameEventArgs eventArgs)
        {
            lastCapturedFrame = (Bitmap)eventArgs.Frame.Clone();
        }

        private void CloseVideoSource()
        {
            if (!(captureDevice == null))
            {
                if (captureDevice.IsRunning)
                {
                    captureDevice.SignalToStop();
                    captureDevice = null;
                }
            }
        }

        public void Dispose()
        {
            Dispose(true);

            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!isDisposed)
            {
                if (disposing)
                {
                    CloseVideoSource();
                    isDisposed = true;
                }
            }
        }
    }
}
